package com.dhms.vostok1.service.ble

import android.annotation.SuppressLint
import android.bluetooth.*
import android.content.Context
import android.os.Build
import android.util.Log
import com.dhms.dirpcandroidlibs.*
import com.dhms.vostok1.data.BLEDevice
import com.dhms.vostok1.data.BLEDeviceType
import com.dhms.vostok1.data.Feature
import com.dhms.vostok1.utils.Utils
import java.util.*

@Suppress("unused")
@SuppressLint("MissingPermission")
class DHMSBleServiceImp private constructor(private val context: Context, ) : BleService(context) {

    private var gatewayManager: GatewayManager? = null
    private var bleServiceCallback: BleServiceCallback? = null
    private var bluetoothGatt: BluetoothGatt? = null//连接设备和服务将数据返回到callback
    private var firmwareFile: ByteArray? = null
    private var totalSent: Int = 0

    override fun connectDevice(device: BluetoothDevice) {
        btAdapter?.cancelDiscovery()
        this.closeConnect()

        if (gatewayManager == null) {
            var retransInterval = 500
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || btAdapter?.isLe2MPhySupported == false) {
                retransInterval = 1500
            }

            GatewayManager.setup("d-app", UUID.randomUUID().toString(), retransInterval)
            gatewayManager = GatewayManager.getInstance()
        }
        Log.d(BLUETOOTH_SERVICE_TAG, "try to connect ${device.address}")
        bluetoothGatt = device.connectGatt(context, false, BluetoothGattListener(device), BluetoothDevice.TRANSPORT_LE)
    }

    /**
     * 关闭连接
     */
    override fun closeConnect() {
        gatewayManager?.disconnect(remote_sn, remote_addr)
        bluetoothGatt?.close()
        Log.d(BLUETOOTH_SERVICE_TAG, "try to close all connections")
    }

    private fun upgradeFirmwareIndHandler(fileName: String, offset: Long, length: Long) {
        if (firmwareFile?.isNotEmpty() == true) {
            Log.d(BLUETOOTH_SERVICE_FIRMWARE_UPGRADE_TAG, "upgrade firmware: $fileName, offset:$offset, length:$length")
            val firmware = ByteArray(length.toInt())
            var cursor: Int
            for (i in (0 until length.toInt())) {
                cursor = offset.toInt() + i
                if (cursor < firmwareFile!!.size) {
                    firmware[i] = firmwareFile!![cursor]
                }
            }
            totalSent += firmware.size
            val progress = totalSent.toFloat() / this.firmwareFile!!.size
            Log.d(BLUETOOTH_SERVICE_FIRMWARE_UPGRADE_TAG, "upgrade firmware($progress): block size: ${firmware.size}")
            gatewayManager?.SentFirmwareData(remote_sn, firmware)
            bleServiceCallback?.onUpgradeResult(progress, null)
            return
        }
        bleServiceCallback?.onUpgradeResult(0f, false)
        Log.e(BLUETOOTH_SERVICE_FIRMWARE_UPGRADE_TAG, "could not read firmware file: $fileName")
    }

    private fun upgradeFirmwareCompletedHandler(succeed: Boolean) {
        Log.d(BLUETOOTH_SERVICE_FIRMWARE_UPGRADE_TAG, "firmware upgrade task is completed with the result: $succeed")
        bleServiceCallback?.onUpgradeResult(1f, succeed)
        firmwareFile = null
        totalSent = 0
    }

    override fun upgrade(device: BluetoothDevice, fileName: String, versionNum: Int, firmware: ByteArray) {
        Log.d(BLUETOOTH_SERVICE_FIRMWARE_UPGRADE_TAG, "upgrade terminal: ${device.name}, firmware: $fileName, fileSize: ${firmware.size}")
        firmwareFile = firmware
        gatewayManager?.UpgradeTerminalAsync(remote_sn, versionNum, fileName, firmware.size.toLong(), ::upgradeFirmwareIndHandler, ::upgradeFirmwareCompletedHandler)
    }

    override fun requestPowerLevel() {
    }

    private fun onGetTerminalInfo(device: BluetoothDevice): (TerminalInfo, CollectParams, TerminalFeatures) -> Unit {
        return fun(info: TerminalInfo, _: CollectParams, f: TerminalFeatures) {
            Log.d(BLUETOOTH_SERVICE_TAG,
                "get terminal info: sn: ${info.TerminalSN}, model: ${info.Model}, hardwareVerison: " +
                        "${info.HWVersion}, numNetInterface: ${info.NumNetInterface}, hwVersionNum: " +
                        "${info.HWVersionNum}, swVersions: ${info.swVersions}, Software: ${info.SwImage} attribute: " +
                        "${info.Attribute}, macAddr: ${info.MacAddr}, otherMacAddr: ${info.OtherMacAddrs}"
            )
            val deviceInfo = BLEDevice(
                device = device,
                type = BLEDeviceType.DHMS_SPC_DEVICE,
                productId = Utils.parseProductId(device.address),
                model = info.Model,
                macAddress = device.address,
                feature = Feature(f.Collects),
                softwareVersions = info.SwImage,
                hwVersionsNum = info.HWVersionNum,
                attr = info.Attribute,
                hwVersion = info.HWVersion,
//                numSwImage = info.numSwImage,
                swFourcc = info.swFourccs.map { l -> l.toInt().toLong() }.toLongArray(),
                swFourccVersions = info.swVersions,
            )
            bleServiceCallback?.onDeviceInfoDataReceived(deviceInfo)
        }
    }

    override fun requestDeviceInfo(device: BluetoothDevice) {
        GatewayManager.getInstance().GetTerminalInfoAsync(
            remote_sn,
            onGetTerminalInfo(device)
        )
    }

//    fun requestDeviceInfo(deviceId: Long) {
//        GatewayManager.getInstance()
//            .GetTerminalInfoAsync(deviceId, ::onGetTerminalInfo)
//    }

    private fun setSpcDeviceTime() {
        val timeStmp = Date().time
        Log.d(BLUETOOTH_SERVICE_TAG, "current time: $timeStmp")
        GatewayManager.getInstance()
            .SetTerminalTime(remote_sn, timeStmp)
    }

    private fun tsDataHandler(collectData: Map<String, Float>) {
        Log.d(BLUETOOTH_SERVICE_TAG, "get ts data: $collectData")

        collectData["TMPR"]?.let { bleServiceCallback?.onTemperatureDataReceived(it) }
        collectData["BVLT"]?.let {
            bleServiceCallback?.onPowerLevelDataReceived(Utils.dhmsSpcDeviceBatteryLevel(it), it)
        }

        bleServiceCallback?.onVibAnalysisResultReceived(collectData)
    }

    override fun requestTemperature() {
        this.measureSpc = arrayOf("TMPR")
        GatewayManager.getInstance()
            .OneShotCollectAsync(
                remote_sn, measureSpc!!,
                ::tsDataHandler,
                null
            )
    }

    private var measureSpc: Array<String>? = null
    override fun requestVibAnalysis() {
        this.measureSpc = arrayOf("VACC")
    }

    private fun blockDataHandler(data: ByteArray) {
        Log.d(
            BLUETOOTH_SERVICE_TAG, "get block data: ${data.size} bytes"
        )
        val result = HashMap<String, Any>()
        result["VACC"] = GatewayManager.getInstance().decodeAiffFile(data).toList()
        result["FILE_DATA"] = data
        result["FILE_TYPE"] = "FILE" // aiff file
        bleServiceCallback?.onVibDataReceived(result)

        val analyseResult: FloatArray = GatewayManager.getInstance().vibAnalysis(data)

        bleServiceCallback?.onVibAnalysisResultReceived(
            mapOf(
                Pair("PACC", analyseResult[0]),
                Pair("RVEL", analyseResult[1]),
                Pair("BDF1", analyseResult[2]),
                Pair("BDF2", analyseResult[3]),
                Pair("BDF3", analyseResult[4]),
                Pair("BDF4", analyseResult[5]),
                Pair("BDF5", analyseResult[6]),
                Pair("FTP1", analyseResult[7]),
                Pair("FTP2", analyseResult[8]),
                Pair("FTP3", analyseResult[9]),
                Pair("FTP4", analyseResult[10]),
                Pair("FTP5", analyseResult[11])
            )
        )
    }

    override fun requestVibData(sample: Long, sampleRate: Long, speed: Long) {
        if (!measureSpc.isNullOrEmpty()) {
            GatewayManager.getInstance()
                .setSensorCollectParams(
                    remote_sn, remote_addr,
                    remote_sn,
                    sample,
                    sampleRate
                )
            GatewayManager.getInstance()
                .OneShotCollectAsync(
                    remote_sn, measureSpc!!,
                    ::tsDataHandler,
                    ::blockDataHandler
                )
        }
    }

    private fun setBleServiceCallback(callback: BleServiceCallback?) {
        bleServiceCallback = callback
    }

    // get gatt service
    private fun getGattService(device: BluetoothDevice, uuid: UUID): BluetoothGattService? {
        if (bluetoothManager.getConnectedDevices(BluetoothProfile.GATT).find {
                it.uuids.contentEquals(
                    device.uuids
                )
            } == null) {
            Log.d(BLUETOOTH_SERVICE_TAG, "without connection")
            return null
        }
        val service = bluetoothGatt?.getService(uuid)
        if (service == null) {
            Log.d(BLUETOOTH_SERVICE_TAG, "without the specific service. uuid: $uuid")
        }
        return service
    }

    private fun writeData(device: BluetoothDevice, msg: ByteArray) {
        val service = getGattService(device, DHMS_BLE_GATT_SERVICE_UUID_FOR_SPOT_CHECK_DEVICE)
        if (service != null) {
            val characteristic =
                service.getCharacteristic(DHMS_BLE_WRITE_CHARACTERISTIC_UUID) // get the writable characteristic
            characteristic.value = msg
            bluetoothGatt?.writeCharacteristic(characteristic)
        }
        return
    }

    inner class BluetoothGattListener(private val device: BluetoothDevice) :BluetoothGattCallback() {
        override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) {
            super.onConnectionStateChange(gatt, status, newState)
            val device = gatt?.device
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                Log.d(BLUETOOTH_SERVICE_TAG, "连接 ${device?.address} 成功！")
                Log.d(BLUETOOTH_SERVICE_TAG, "请求设置BLE MTU 位 $MTU")
                gatt?.requestMtu(MTU)
                // delay the onConnected event after the read gatt service ready
                // deviceConnectionCallback.onConnected(device)
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                gatewayManager?.unRegisterDirpcProtocolHandler()
                Log.d(BLUETOOTH_SERVICE_TAG, "连接失败或断连成功：${device?.address} $status ")
                bleServiceCallback?.onDisconnected()
            }
        }

        override fun onMtuChanged(gatt: BluetoothGatt?, mtu: Int, status: Int) {
            super.onMtuChanged(gatt, mtu, status)
            if (status == BluetoothGatt.GATT_SUCCESS) {
                Log.d(
                    BLUETOOTH_SERVICE_TAG,
                    "BLE MTU is changed to $mtu, starting to discover services."
                )
                gatt?.discoverServices()
            } else {
                Log.d(BLUETOOTH_SERVICE_TAG, "BLE MTU setting failed. mtu: $mtu")
            }
        }

        override fun onPhyUpdate(gatt: BluetoothGatt?, txPhy: Int, rxPhy: Int, status: Int) {
            super.onPhyUpdate(gatt, txPhy, rxPhy, status)
            Log.d(
                BLUETOOTH_SERVICE_TAG,
                "BLE Phy is changed to txPhy:$txPhy, rxPhy:$rxPhy, status:$status"
            )
        }

        override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) {
            super.onServicesDiscovered(gatt, status)
            if (status == BluetoothGatt.GATT_SUCCESS) {
                Log.d(BLUETOOTH_SERVICE_TAG, "on gatt server discovered success")

                gatt?.services?.forEach { service ->
                    Log.d(BLUETOOTH_SERVICE_TAG, "service 的 uuid: ${service.uuid}")
                    service.characteristics.forEach { characteristic ->
                        Log.d(
                            BLUETOOTH_SERVICE_TAG,
                            "characteristic 的 uuid: ${characteristic.uuid}"
                        )
                    }
                }

                val dhmsService =
                    getGattService(device, DHMS_BLE_GATT_SERVICE_UUID_FOR_SPOT_CHECK_DEVICE)
                if (dhmsService != null) {
                    val notificationGattCharacteristic =
                        dhmsService.getCharacteristic(DHMS_BLE_NOTIFY_SERVICE_CHARACTERISTIC_UUID)
                    if (notificationGattCharacteristic != null &&
                        dhmsService.getCharacteristic(DHMS_BLE_WRITE_CHARACTERISTIC_UUID) != null
                    ) {
                        enableNotification(notificationGattCharacteristic)
                    }
                }
            } else {
                Log.d(BLUETOOTH_SERVICE_TAG, "on gatt server discovered fail")
            }
        }

        override fun onCharacteristicRead(
            gatt: BluetoothGatt?,
            characteristic: BluetoothGattCharacteristic?,
            status: Int
        ) {
            super.onCharacteristicRead(gatt, characteristic, status)
            characteristic?.let {
                val data = String(it.value)
                Log.d(BLUETOOTH_SERVICE_TAG, "CharacteristicRead 数据: $data")
            }
        }

        override fun onCharacteristicWrite(
            gatt: BluetoothGatt?,
            characteristic: BluetoothGattCharacteristic?,
            status: Int
        ) {
            super.onCharacteristicWrite(gatt, characteristic, status)
            characteristic?.let {
                val data = it.value
                val hexDataBuffer: StringBuilder = java.lang.StringBuilder()
                for ((i, b) in data.withIndex()) {
                    if (i == 20) {
                        hexDataBuffer.append(" | ")
                    }
                    hexDataBuffer.append(String.format("%02X ", b))
                }

                Log.d(BLUETOOTH_SERVICE_TAG, "发送 数据(Size:${data.size}):${hexDataBuffer}")
            }
        }

        /**
         * get notified data
         */
        override fun onCharacteristicChanged(gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic?) {
            super.onCharacteristicChanged(gatt, characteristic)
            characteristic?.let {
                val data = it.value
                val hexDataBuffer: StringBuilder = java.lang.StringBuilder()
                for ((i, b) in data.withIndex()) {
                    if (i == 20) {
                        hexDataBuffer.append(" | ")
                    }
                    hexDataBuffer.append(String.format("%02X ", b))
                }
                val frameNumbers = data.asList().subList(12, 17)
                Log.d(
                    BLUETOOTH_SERVICE_TAG,
                    "接收第${
                        (frameNumbers[0].toUInt() and 255u shl 24) or
                                (frameNumbers[1].toUInt() and 255u shl 16) or
                                (frameNumbers[2].toUInt() and 255u shl 8) or
                                (frameNumbers[3].toUInt() and 255u)
                    }帧数据(Size:${data.size}：${hexDataBuffer}"
                )
                gatewayManager?.OnBleDataReceived(data)
            }
        }

        override fun onDescriptorRead(
            gatt: BluetoothGatt?,
            descriptor: BluetoothGattDescriptor?,
            status: Int
        ) {
            super.onDescriptorRead(gatt, descriptor, status)
            descriptor?.let {
                val hexDataBuffer: StringBuilder = java.lang.StringBuilder()
                for (b in it.value) {
                    hexDataBuffer.append(String.format("%02X ", b))
                }
                Log.d(BLUETOOTH_SERVICE_TAG, "DescriptorRead 数据: $hexDataBuffer")
            }
        }

        override fun onDescriptorWrite(
            gatt: BluetoothGatt?,
            descriptor: BluetoothGattDescriptor?,
            status: Int
        ) {
            super.onDescriptorWrite(gatt, descriptor, status)
            descriptor?.let {
                val hexDataBuffer: StringBuilder = java.lang.StringBuilder()
                for ((i, b) in it.value.withIndex()) {
                    if (i == 20) {
                        hexDataBuffer.append(" | ")
                    }
                    hexDataBuffer.append(String.format("%02X ", b))
                }
                Log.d(
                    BLUETOOTH_SERVICE_TAG,
                    "发送 数据(Size:${it.value.size}): $hexDataBuffer"
                )
            }
        }

        /**
         * assign the notification service and descriptor uuid
         */
        private fun enableNotification(notificationGattCharacteristic: BluetoothGattCharacteristic) {
            bluetoothGatt?.setCharacteristicNotification(notificationGattCharacteristic, true)
            val descriptor =
                notificationGattCharacteristic.getDescriptor(DHMS_BLE_NOTIFICATION_DESCRIPTOR_UUID)
                    ?.apply {
                        value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
                    }
            bluetoothGatt?.writeDescriptor(descriptor)
            gatewayManager?.let {
                it.registerDirpcProtocolHandler(object : IDirpcProtocolHandler {
                    override fun dirpcConnectionChanged(connected: Boolean) {
                        if (connected) {
                            Log.d(BLUETOOTH_SERVICE_TAG, "dirpcConnectionChanged: true")
                            bleServiceCallback?.onConnected()
                            setSpcDeviceTime()
                        } else {
                            Log.d(BLUETOOTH_SERVICE_TAG, "dirpcConnectionChanged: false")
                            bluetoothGatt?.close()
                            bleServiceCallback?.onDisconnected()
                        }
                    }

                    override fun dirpcSend(data: ByteArray) {
                        writeData(device, data)
                    }
                })
                it.connect(remote_sn, remote_addr)
            }
        }
    }

    companion object {
        const val MTU: Int = 247
        const val SCAN_DURATION_SECOND: Long = 10
        const val BLUETOOTH_SERVICE_FIRMWARE_UPGRADE_TAG: String = "FIRMWARE_UPGRADE"
        val BLUETOOTH_SERVICE_TAG: String = DHMSBleServiceImp::class.java.simpleName
        val DHMS_BLE_GATT_SERVICE_UUID_FOR_SPOT_CHECK_DEVICE: UUID =
            UUID.fromString("a6ed0201-d344-460a-8075-b9e8ec90d71b")
        val DHMS_BLE_NOTIFY_SERVICE_CHARACTERISTIC_UUID: UUID =
            UUID.fromString("a6ed0202-d344-460a-8075-b9e8ec90d71b")
        val DHMS_BLE_NOTIFICATION_DESCRIPTOR_UUID: UUID =
            UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
        val DHMS_BLE_WRITE_CHARACTERISTIC_UUID: UUID =
            UUID.fromString("a6ed0203-d344-460a-8075-b9e8ec90d71b")
        const val remote_sn = 1163149639L
        const val remote_addr = "local"

        @SuppressLint("StaticFieldLeak")
        private var instance: DHMSBleServiceImp? = null

        @JvmStatic
        @Synchronized
        fun getInstance(
            context: Context,
            callback: BleServiceCallback?
        ): DHMSBleServiceImp {
            if (instance == null) {
                instance = DHMSBleServiceImp(context.applicationContext)
            }
            instance!!.setBleServiceCallback(callback)
            return instance!!
        }
    }
}